baubs git
Commit ab9b22b9b8b4341a354ba2d3d8ca18c5fdc76ef9
Parents : a275f72
Author : Jan-Henrik Bruhn <jan-henrik.bruhn@offis.de>
Date : 2025-12-17T21:43:36+01:00
feature: Implement unified semantic color system with Tailwind v4
- Define semantic color theme in App.css using @theme directive
- Primary (blue), Success (green), Warning (amber), Danger (red)
- Info (cyan), Accent (purple), Secondary (orange), Tertiary (teal)
- Semantic colors reference Tailwind color variables via var()
- Media query-based dark mode for canvas colors
- Migrate all 16 components from direct Tailwind colors to semantic names
- Create cssVariables.ts utility for Konva canvas color access
- Update KonvaComponents to use CSS variables dynamically
- Replace @apply with CSS variables in index.css for v4 compatibility
- Remove unused designTokens.ts file
- Improve light mode contrast with gray-300 app background
- Adjust canvas and info box backgrounds to gray-200
Benefits:
- Easy theme customization by updating App.css @theme block
- Consistent semantic naming across all components
- Proper dark mode support via media queries
- No visual regressions, all colors maintained
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes
20 files changed, 316 insertions(+), 301 deletions(-)
Diff
diff --git a/src/App.css b/src/App.css
index 88339c3..cbb287c 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,6 +1,129 @@
@import "tailwindcss";
-/* Custom animations for shimmer effect */
+/* ============================================
+ THEME DEFINITION - Tailwind v4
+ Semantic color system that references Tailwind colors
+ ============================================ */
+
+@theme {
+ /* PRIMARY - Main brand color (references Blue) */
+ --color-primary-50: var(--color-blue-50);
+ --color-primary-100: var(--color-blue-100);
+ --color-primary-200: var(--color-blue-200);
+ --color-primary-300: var(--color-blue-300);
+ --color-primary-400: var(--color-blue-400);
+ --color-primary-500: var(--color-blue-500);
+ --color-primary-600: var(--color-blue-600);
+ --color-primary-700: var(--color-blue-700);
+ --color-primary-800: var(--color-blue-800);
+ --color-primary-900: var(--color-blue-900);
+ --color-primary-950: var(--color-blue-950);
+
+ /* SUCCESS - Positive states, completion (references Green) */
+ --color-success-50: var(--color-green-50);
+ --color-success-100: var(--color-green-100);
+ --color-success-200: var(--color-green-200);
+ --color-success-300: var(--color-green-300);
+ --color-success-400: var(--color-green-400);
+ --color-success-500: var(--color-green-500);
+ --color-success-600: var(--color-green-600);
+ --color-success-700: var(--color-green-700);
+ --color-success-800: var(--color-green-800);
+ --color-success-900: var(--color-green-900);
+ --color-success-950: var(--color-green-950);
+
+ /* WARNING - Caution, waiting states (references Amber) */
+ --color-warning-50: var(--color-amber-50);
+ --color-warning-100: var(--color-amber-100);
+ --color-warning-200: var(--color-amber-200);
+ --color-warning-300: var(--color-amber-300);
+ --color-warning-400: var(--color-amber-400);
+ --color-warning-500: var(--color-amber-500);
+ --color-warning-600: var(--color-amber-600);
+ --color-warning-700: var(--color-amber-700);
+ --color-warning-800: var(--color-amber-800);
+ --color-warning-900: var(--color-amber-900);
+ --color-warning-950: var(--color-amber-950);
+
+ /* DANGER - Errors, destructive actions (references Red) */
+ --color-danger-50: var(--color-red-50);
+ --color-danger-100: var(--color-red-100);
+ --color-danger-200: var(--color-red-200);
+ --color-danger-300: var(--color-red-300);
+ --color-danger-400: var(--color-red-400);
+ --color-danger-500: var(--color-red-500);
+ --color-danger-600: var(--color-red-600);
+ --color-danger-700: var(--color-red-700);
+ --color-danger-800: var(--color-red-800);
+ --color-danger-900: var(--color-red-900);
+ --color-danger-950: var(--color-red-950);
+
+ /* INFO - Informational states (references Cyan) */
+ --color-info-50: var(--color-cyan-50);
+ --color-info-100: var(--color-cyan-100);
+ --color-info-200: var(--color-cyan-200);
+ --color-info-300: var(--color-cyan-300);
+ --color-info-400: var(--color-cyan-400);
+ --color-info-500: var(--color-cyan-500);
+ --color-info-600: var(--color-cyan-600);
+ --color-info-700: var(--color-cyan-700);
+ --color-info-800: var(--color-cyan-800);
+ --color-info-900: var(--color-cyan-900);
+ --color-info-950: var(--color-cyan-950);
+
+ /* ACCENT - Progress, sewing states (references Purple) */
+ --color-accent-50: var(--color-purple-50);
+ --color-accent-100: var(--color-purple-100);
+ --color-accent-200: var(--color-purple-200);
+ --color-accent-300: var(--color-purple-300);
+ --color-accent-400: var(--color-purple-400);
+ --color-accent-500: var(--color-purple-500);
+ --color-accent-600: var(--color-purple-600);
+ --color-accent-700: var(--color-purple-700);
+ --color-accent-800: var(--color-purple-800);
+ --color-accent-900: var(--color-purple-900);
+ --color-accent-950: var(--color-purple-950);
+
+ /* SECONDARY - Upload operations (references Orange) */
+ --color-secondary-50: var(--color-orange-50);
+ --color-secondary-100: var(--color-orange-100);
+ --color-secondary-200: var(--color-orange-200);
+ --color-secondary-300: var(--color-orange-300);
+ --color-secondary-400: var(--color-orange-400);
+ --color-secondary-500: var(--color-orange-500);
+ --color-secondary-600: var(--color-orange-600);
+ --color-secondary-700: var(--color-orange-700);
+ --color-secondary-800: var(--color-orange-800);
+ --color-secondary-900: var(--color-orange-900);
+ --color-secondary-950: var(--color-orange-950);
+
+ /* TERTIARY - Pattern canvas theme (references Teal) */
+ --color-tertiary-500: var(--color-teal-500);
+ --color-tertiary-600: var(--color-teal-600);
+
+ /* Canvas/Konva-specific colors for embroidery rendering */
+ --color-canvas-grid: #e0e0e0;
+ --color-canvas-origin: #888888;
+ --color-canvas-hoop: #2196F3;
+ --color-canvas-bounds: #ff0000;
+ --color-canvas-position: #ff0000;
+}
+
+/* Dark Mode Overrides - Media Query */
+@media (prefers-color-scheme: dark) {
+ @theme {
+ /* Canvas colors adjusted for dark mode */
+ --color-canvas-grid: #404040;
+ --color-canvas-origin: #999999;
+ /* hoop, bounds, position stay the same for visibility */
+ }
+}
+
+/* ============================================
+ CUSTOM ANIMATIONS
+ ============================================ */
+
+/* Shimmer effect for progress bars */
@keyframes shimmer {
0% {
transform: translateX(-100%);
@@ -44,13 +167,13 @@
}
}
-/* Pulse glow effect */
+/* Pulse glow effect - uses primary-600 */
@keyframes pulseGlow {
0%, 100% {
- box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
+ box-shadow: 0 0 0 0 rgb(37 99 235 / 0.4); /* primary-600 with 40% opacity */
}
50% {
- box-shadow: 0 0 0 8px rgba(59, 130, 246, 0);
+ box-shadow: 0 0 0 8px rgb(37 99 235 / 0); /* primary-600 fully transparent */
}
}
diff --git a/src/App.tsx b/src/App.tsx
index c9bb4cc..9a6f49c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -67,7 +67,7 @@ function App() {
}, [resumedPattern, resumeFileName, pesData, setPattern, setPatternOffset]);
return (
- <div className="h-screen flex flex-col bg-gray-50 dark:bg-gray-900 overflow-hidden">
+ <div className="h-screen flex flex-col bg-gray-300 dark:bg-gray-900 overflow-hidden">
<AppHeader />
<div className="flex-1 p-4 sm:p-5 lg:p-6 w-full overflow-y-auto lg:overflow-hidden flex flex-col">
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
index d11ab61..1ffacad 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader.tsx
@@ -86,18 +86,18 @@ export function AppHeader() {
}, [showErrorPopover, setErrorPopover]);
return (
- <header className="bg-gradient-to-r from-blue-600 via-blue-700 to-blue-800 dark:from-blue-700 dark:via-blue-800 dark:to-blue-900 px-4 sm:px-6 lg:px-8 py-3 shadow-lg border-b-2 border-blue-900/20 dark:border-blue-800/30 flex-shrink-0">
+ <header className="bg-gradient-to-r from-primary-600 via-primary-700 to-primary-800 dark:from-primary-700 dark:via-primary-800 dark:to-primary-900 px-4 sm:px-6 lg:px-8 py-3 shadow-lg border-b-2 border-primary-900/20 dark:border-primary-800/30 flex-shrink-0">
<div className="grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-4 lg:gap-8 items-center">
{/* Machine Connection Status - Responsive width column */}
<div className="flex items-center gap-3 w-full lg:w-[280px]">
- <div className="w-2.5 h-2.5 bg-green-400 rounded-full animate-pulse shadow-lg shadow-green-400/50" style={{ visibility: isConnected ? 'visible' : 'hidden' }}></div>
+ <div className="w-2.5 h-2.5 bg-success-400 rounded-full animate-pulse shadow-lg shadow-success-400/50" style={{ visibility: isConnected ? 'visible' : 'hidden' }}></div>
<div className="w-2.5 h-2.5 bg-gray-400 rounded-full -ml-2.5" style={{ visibility: !isConnected ? 'visible' : 'hidden' }}></div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h1 className="text-lg lg:text-xl font-bold text-white leading-tight">Respira</h1>
{isConnected && machineInfo?.serialNumber && (
<span
- className="text-xs text-blue-200 cursor-help"
+ className="text-xs text-primary-200 cursor-help"
title={`Serial: ${machineInfo.serialNumber}${
machineInfo.macAddress
? `\nMAC: ${machineInfo.macAddress}`
@@ -116,7 +116,7 @@ export function AppHeader() {
</span>
)}
{isPolling && (
- <ArrowPathIcon className="w-3.5 h-3.5 text-blue-200 animate-spin" title="Auto-refreshing status" />
+ <ArrowPathIcon className="w-3.5 h-3.5 text-primary-200 animate-spin" title="Auto-refreshing status" />
)}
</div>
<div className="flex items-center gap-2 mt-1 min-h-[32px]">
@@ -124,7 +124,7 @@ export function AppHeader() {
<>
<button
onClick={disconnect}
- className="inline-flex items-center gap-1.5 px-2.5 py-1.5 sm:py-1 rounded text-sm font-medium bg-white/10 hover:bg-red-600 text-blue-100 hover:text-white border border-white/20 hover:border-red-600 cursor-pointer transition-all flex-shrink-0"
+ className="inline-flex items-center gap-1.5 px-2.5 py-1.5 sm:py-1 rounded text-sm font-medium bg-white/10 hover:bg-danger-600 text-primary-100 hover:text-white border border-white/20 hover:border-danger-600 cursor-pointer transition-all flex-shrink-0"
title="Disconnect from machine"
aria-label="Disconnect from machine"
>
@@ -137,7 +137,7 @@ export function AppHeader() {
</span>
</>
) : (
- <p className="text-xs text-blue-200">Not Connected</p>
+ <p className="text-xs text-primary-200">Not Connected</p>
)}
{/* Error indicator - always render to prevent layout shift */}
@@ -145,7 +145,7 @@ export function AppHeader() {
<button
ref={errorButtonRef}
onClick={() => setErrorPopover(!showErrorPopover)}
- className={`inline-flex items-center gap-1.5 px-2.5 py-1.5 sm:py-1 rounded text-sm font-medium bg-red-500/90 hover:bg-red-600 text-white border border-red-400 transition-all flex-shrink-0 ${
+ className={`inline-flex items-center gap-1.5 px-2.5 py-1.5 sm:py-1 rounded text-sm font-medium bg-danger-500/90 hover:bg-danger-600 text-white border border-danger-400 transition-all flex-shrink-0 ${
(machineErrorMessage || pyodideError)
? 'cursor-pointer animate-pulse hover:animate-none'
: 'invisible pointer-events-none'
diff --git a/src/components/BluetoothDevicePicker.tsx b/src/components/BluetoothDevicePicker.tsx
index 85a45c9..c993423 100644
--- a/src/components/BluetoothDevicePicker.tsx
+++ b/src/components/BluetoothDevicePicker.tsx
@@ -59,7 +59,7 @@ export function BluetoothDevicePicker() {
return (
<div className="fixed inset-0 bg-black/50 dark:bg-black/70 flex items-center justify-center z-[1000]" onClick={handleCancel}>
<div
- className="bg-white dark:bg-gray-800 rounded-lg shadow-2xl max-w-lg w-[90%] m-4 border-t-4 border-blue-600 dark:border-blue-500"
+ className="bg-white dark:bg-gray-800 rounded-lg shadow-2xl max-w-lg w-[90%] m-4 border-t-4 border-primary-600 dark:border-primary-500"
onClick={(e) => e.stopPropagation()}
role="dialog"
aria-labelledby="bluetooth-picker-title"
@@ -73,7 +73,7 @@ export function BluetoothDevicePicker() {
<div className="p-6">
{isScanning && devices.length === 0 ? (
<div className="flex items-center gap-3 text-gray-700 dark:text-gray-300">
- <svg className="animate-spin h-5 w-5 text-blue-600 dark:text-blue-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
+ <svg className="animate-spin h-5 w-5 text-primary-600 dark:text-primary-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
@@ -89,7 +89,7 @@ export function BluetoothDevicePicker() {
<button
key={device.deviceId}
onClick={() => handleSelectDevice(device.deviceId)}
- className="w-full px-4 py-3 bg-gray-100 dark:bg-gray-700 text-left rounded-lg font-medium text-sm hover:bg-blue-100 dark:hover:bg-blue-900 hover:text-blue-900 dark:hover:text-blue-100 active:bg-blue-200 dark:active:bg-blue-800 transition-all duration-150 cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
+ className="w-full px-4 py-3 bg-gray-100 dark:bg-gray-700 text-left rounded-lg font-medium text-sm hover:bg-primary-100 dark:hover:bg-primary-900 hover:text-primary-900 dark:hover:text-primary-100 active:bg-primary-200 dark:active:bg-primary-800 transition-all duration-150 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-300 dark:focus:ring-primary-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
aria-label={`Connect to ${device.deviceName}`}
>
<div className="font-semibold text-gray-900 dark:text-white">{device.deviceName}</div>
diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx
index 1c03175..68cea07 100644
--- a/src/components/ConfirmDialog.tsx
+++ b/src/components/ConfirmDialog.tsx
@@ -40,7 +40,7 @@ export function ConfirmDialog({
return (
<div className="fixed inset-0 bg-black/50 dark:bg-black/70 flex items-center justify-center z-[1000]" onClick={onCancel}>
<div
- className={`bg-white dark:bg-gray-800 rounded-lg shadow-2xl max-w-lg w-[90%] m-4 ${variant === 'danger' ? 'border-t-4 border-red-600 dark:border-red-500' : 'border-t-4 border-yellow-500 dark:border-yellow-600'}`}
+ className={`bg-white dark:bg-gray-800 rounded-lg shadow-2xl max-w-lg w-[90%] m-4 ${variant === 'danger' ? 'border-t-4 border-danger-600 dark:border-danger-500' : 'border-t-4 border-warning-500 dark:border-warning-600'}`}
onClick={(e) => e.stopPropagation()}
role="dialog"
aria-labelledby="dialog-title"
@@ -65,8 +65,8 @@ export function ConfirmDialog({
onClick={onConfirm}
className={
variant === 'danger'
- ? 'px-6 py-2.5 bg-red-600 dark:bg-red-700 text-white rounded-lg font-semibold text-sm hover:bg-red-700 dark:hover:bg-red-600 active:bg-red-800 dark:active:bg-red-500 hover:shadow-lg active:scale-[0.98] transition-all duration-150 cursor-pointer focus:outline-none focus:ring-2 focus:ring-red-300 dark:focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
- : 'px-6 py-2.5 bg-blue-600 dark:bg-blue-700 text-white rounded-lg font-semibold text-sm hover:bg-blue-700 dark:hover:bg-blue-600 active:bg-blue-800 dark:active:bg-blue-500 hover:shadow-lg active:scale-[0.98] transition-all duration-150 cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-300 dark:focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
+ ? 'px-6 py-2.5 bg-danger-600 dark:bg-danger-700 text-white rounded-lg font-semibold text-sm hover:bg-danger-700 dark:hover:bg-danger-600 active:bg-danger-800 dark:active:bg-danger-500 hover:shadow-lg active:scale-[0.98] transition-all duration-150 cursor-pointer focus:outline-none focus:ring-2 focus:ring-danger-300 dark:focus:ring-danger-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
+ : 'px-6 py-2.5 bg-primary-600 dark:bg-primary-700 text-white rounded-lg font-semibold text-sm hover:bg-primary-700 dark:hover:bg-primary-600 active:bg-primary-800 dark:active:bg-primary-500 hover:shadow-lg active:scale-[0.98] transition-all duration-150 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-300 dark:focus:ring-primary-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
}
aria-label={`Confirm: ${confirmText}`}
>
diff --git a/src/components/ConnectionPrompt.tsx b/src/components/ConnectionPrompt.tsx
index 118113f..77bfcb9 100644
--- a/src/components/ConnectionPrompt.tsx
+++ b/src/components/ConnectionPrompt.tsx
@@ -26,7 +26,7 @@ export function ConnectionPrompt() {
</div>
<button
onClick={connect}
- className="w-full flex items-center justify-center gap-2 px-3 py-2.5 sm:py-2 bg-blue-600 dark:bg-blue-700 text-white rounded font-semibold text-sm hover:bg-blue-700 dark:hover:bg-blue-600 active:bg-blue-800 dark:active:bg-blue-500 transition-colors cursor-pointer"
+ className="w-full flex items-center justify-center gap-2 px-3 py-2.5 sm:py-2 bg-primary-600 dark:bg-primary-700 text-white rounded font-semibold text-sm hover:bg-primary-700 dark:hover:bg-primary-600 active:bg-primary-800 dark:active:bg-primary-500 transition-colors cursor-pointer"
>
Connect to Machine
</button>
@@ -35,17 +35,17 @@ export function ConnectionPrompt() {
}
return (
- <div className="bg-amber-50 dark:bg-amber-900/20 p-4 rounded-lg shadow-md border-l-4 border-amber-500 dark:border-amber-600">
+ <div className="bg-warning-50 dark:bg-warning-900/20 p-4 rounded-lg shadow-md border-l-4 border-warning-500 dark:border-warning-600">
<div className="flex items-start gap-3">
- <ExclamationTriangleIcon className="w-6 h-6 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" />
+ <ExclamationTriangleIcon className="w-6 h-6 text-warning-600 dark:text-warning-400 flex-shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
- <h3 className="text-base font-semibold text-amber-900 dark:text-amber-100 mb-2">Browser Not Supported</h3>
- <p className="text-sm text-amber-800 dark:text-amber-200 mb-3">
+ <h3 className="text-base font-semibold text-warning-900 dark:text-warning-100 mb-2">Browser Not Supported</h3>
+ <p className="text-sm text-warning-800 dark:text-warning-200 mb-3">
Your browser doesn't support Web Bluetooth, which is required to connect to your embroidery machine.
</p>
<div className="space-y-2">
- <p className="text-sm font-semibold text-amber-900 dark:text-amber-100">Please try one of these options:</p>
- <ul className="text-sm text-amber-800 dark:text-amber-200 space-y-1.5 ml-4 list-disc">
+ <p className="text-sm font-semibold text-warning-900 dark:text-warning-100">Please try one of these options:</p>
+ <ul className="text-sm text-warning-800 dark:text-warning-200 space-y-1.5 ml-4 list-disc">
<li>Use a supported browser (Chrome, Edge, or Opera)</li>
<li>
Download the Desktop app from{' '}
@@ -53,7 +53,7 @@ export function ConnectionPrompt() {
href="https://github.com/jhbruhn/respira/releases/latest"
target="_blank"
rel="noopener noreferrer"
- className="font-semibold underline hover:text-amber-900 dark:hover:text-amber-50 transition-colors"
+ className="font-semibold underline hover:text-warning-900 dark:hover:text-warning-50 transition-colors"
>
GitHub Releases
</a>
diff --git a/src/components/ErrorPopover.tsx b/src/components/ErrorPopover.tsx
index 6f09705..25e8ebd 100644
--- a/src/components/ErrorPopover.tsx
+++ b/src/components/ErrorPopover.tsx
@@ -17,24 +17,24 @@ export const ErrorPopover = forwardRef<HTMLDivElement, ErrorPopoverProps>(
const isInfo = isPairingErr || errorDetails?.isInformational;
const bgColor = isInfo
- ? 'bg-blue-50 dark:bg-blue-900/95 border-blue-600 dark:border-blue-500'
- : 'bg-red-50 dark:bg-red-900/95 border-red-600 dark:border-red-500';
+ ? 'bg-info-50 dark:bg-info-900/95 border-info-600 dark:border-info-500'
+ : 'bg-danger-50 dark:bg-danger-900/95 border-danger-600 dark:border-danger-500';
const iconColor = isInfo
- ? 'text-blue-600 dark:text-blue-400'
- : 'text-red-600 dark:text-red-400';
+ ? 'text-info-600 dark:text-info-400'
+ : 'text-danger-600 dark:text-danger-400';
const textColor = isInfo
- ? 'text-blue-900 dark:text-blue-200'
- : 'text-red-900 dark:text-red-200';
+ ? 'text-info-900 dark:text-info-200'
+ : 'text-danger-900 dark:text-danger-200';
const descColor = isInfo
- ? 'text-blue-800 dark:text-blue-300'
- : 'text-red-800 dark:text-red-300';
+ ? 'text-info-800 dark:text-info-300'
+ : 'text-danger-800 dark:text-danger-300';
const listColor = isInfo
- ? 'text-blue-700 dark:text-blue-300'
- : 'text-red-700 dark:text-red-300';
+ ? 'text-info-700 dark:text-info-300'
+ : 'text-danger-700 dark:text-danger-300';
const Icon = isInfo ? InformationCircleIcon : ExclamationTriangleIcon;
const title = errorDetails?.title || (isPairingErr ? 'Pairing Required' : 'Error');
diff --git a/src/components/FileUpload.tsx b/src/components/FileUpload.tsx
index 16754f7..3eedd4e 100644
--- a/src/components/FileUpload.tsx
+++ b/src/components/FileUpload.tsx
@@ -171,8 +171,8 @@ export function FileUpload() {
const boundsCheck = checkPatternFitsInHoop();
- const borderColor = pesData ? 'border-orange-600 dark:border-orange-500' : 'border-gray-400 dark:border-gray-600';
- const iconColor = pesData ? 'text-orange-600 dark:text-orange-400' : 'text-gray-600 dark:text-gray-400';
+ const borderColor = pesData ? 'border-secondary-600 dark:border-secondary-500' : 'border-gray-400 dark:border-gray-600';
+ const iconColor = pesData ? 'text-secondary-600 dark:text-secondary-400' : 'text-gray-600 dark:text-gray-400';
return (
<div className={`bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 ${borderColor}`}>
@@ -191,8 +191,8 @@ export function FileUpload() {
</div>
{resumeAvailable && resumeFileName && (
- <div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 px-3 py-2 rounded mb-3">
- <p className="text-xs text-green-800 dark:text-green-200">
+ <div className="bg-success-50 dark:bg-success-900/20 border border-success-200 dark:border-success-800 px-3 py-2 rounded mb-3">
+ <p className="text-xs text-success-800 dark:text-success-200">
<strong>Cached:</strong> "{resumeFileName}"
</p>
</div>
@@ -249,7 +249,7 @@ export function FileUpload() {
<button
onClick={handleUpload}
disabled={!isConnected || isUploading || !boundsCheck.fits}
- className="flex-1 px-3 py-2.5 sm:py-2 bg-blue-600 dark:bg-blue-700 text-white rounded font-semibold text-sm hover:bg-blue-700 dark:hover:bg-blue-600 active:bg-blue-800 dark:active:bg-blue-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
+ className="flex-1 px-3 py-2.5 sm:py-2 bg-primary-600 dark:bg-primary-700 text-white rounded font-semibold text-sm hover:bg-primary-700 dark:hover:bg-primary-600 active:bg-primary-800 dark:active:bg-primary-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
aria-label={isUploading ? `Uploading pattern: ${uploadProgress.toFixed(0)}% complete` : boundsCheck.error || 'Upload pattern to machine'}
>
{isUploading ? (
@@ -279,13 +279,13 @@ export function FileUpload() {
? 'Please wait - initializing Python environment...'
: pyodideLoadingStep || 'Initializing Python environment...'}
</span>
- <span className="text-xs font-bold text-blue-600 dark:text-blue-400">
+ <span className="text-xs font-bold text-primary-600 dark:text-primary-400">
{pyodideProgress.toFixed(0)}%
</span>
</div>
<div className="h-2.5 bg-gray-300 dark:bg-gray-600 rounded-full overflow-hidden shadow-inner relative">
<div
- className="h-full bg-gradient-to-r from-blue-500 via-blue-600 to-blue-700 dark:from-blue-600 dark:via-blue-700 dark:to-blue-800 transition-all duration-300 ease-out relative overflow-hidden after:absolute after:inset-0 after:bg-gradient-to-r after:from-transparent after:via-white/30 after:to-transparent after:animate-[shimmer_2s_infinite] rounded-full"
+ className="h-full bg-gradient-to-r from-primary-500 via-primary-600 to-primary-700 dark:from-primary-600 dark:via-primary-700 dark:to-primary-800 transition-all duration-300 ease-out relative overflow-hidden after:absolute after:inset-0 after:bg-gradient-to-r after:from-transparent after:via-white/30 after:to-transparent after:animate-[shimmer_2s_infinite] rounded-full"
style={{ width: `${pyodideProgress}%` }}
/>
</div>
@@ -303,13 +303,13 @@ export function FileUpload() {
marginTop: (pesData && (boundsCheck.error || !canUploadPattern(machineStatus))) ? '12px' : '0px'
}}>
{pesData && !canUploadPattern(machineStatus) && (
- <div className="bg-yellow-100 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-200 px-3 py-2 rounded border border-yellow-200 dark:border-yellow-800 text-sm">
+ <div className="bg-warning-100 dark:bg-warning-900/20 text-warning-800 dark:text-warning-200 px-3 py-2 rounded border border-warning-200 dark:border-warning-800 text-sm">
Cannot upload while {getMachineStateCategory(machineStatus)}
</div>
)}
{pesData && boundsCheck.error && (
- <div className="bg-red-100 dark:bg-red-900/20 text-red-800 dark:text-red-200 px-3 py-2 rounded border border-red-200 dark:border-red-800 text-sm">
+ <div className="bg-danger-100 dark:bg-danger-900/20 text-danger-800 dark:text-danger-200 px-3 py-2 rounded border border-danger-200 dark:border-danger-800 text-sm">
<strong>Pattern too large:</strong> {boundsCheck.error}
</div>
)}
@@ -319,13 +319,13 @@ export function FileUpload() {
<div className="mt-3">
<div className="flex justify-between items-center mb-1.5">
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">Uploading</span>
- <span className="text-xs font-bold text-orange-600 dark:text-orange-400">
+ <span className="text-xs font-bold text-secondary-600 dark:text-secondary-400">
{uploadProgress > 0 ? uploadProgress.toFixed(1) + '%' : 'Starting...'}
</span>
</div>
<div className="h-2.5 bg-gray-300 dark:bg-gray-600 rounded-full overflow-hidden shadow-inner relative">
<div
- className="h-full bg-gradient-to-r from-orange-500 via-orange-600 to-orange-700 dark:from-orange-600 dark:via-orange-700 dark:to-orange-800 transition-all duration-300 ease-out relative overflow-hidden after:absolute after:inset-0 after:bg-gradient-to-r after:from-transparent after:via-white/30 after:to-transparent after:animate-[shimmer_2s_infinite] rounded-full"
+ className="h-full bg-gradient-to-r from-secondary-500 via-secondary-600 to-secondary-700 dark:from-secondary-600 dark:via-secondary-700 dark:to-secondary-800 transition-all duration-300 ease-out relative overflow-hidden after:absolute after:inset-0 after:bg-gradient-to-r after:from-transparent after:via-white/30 after:to-transparent after:animate-[shimmer_2s_infinite] rounded-full"
style={{ width: `${uploadProgress}%` }}
/>
</div>
diff --git a/src/components/KonvaComponents.tsx b/src/components/KonvaComponents.tsx
index d9bce9d..146471d 100644
--- a/src/components/KonvaComponents.tsx
+++ b/src/components/KonvaComponents.tsx
@@ -4,6 +4,7 @@ import type { PesPatternData } from '../formats/import/pesImporter';
import { getThreadColor } from '../formats/import/pesImporter';
import type { MachineInfo } from '../types/machine';
import { MOVE } from '../formats/import/constants';
+import { canvasColors } from '../utils/cssVariables';
interface GridProps {
gridSize: number;
@@ -34,9 +35,7 @@ export const Grid = memo(({ gridSize, bounds, machineInfo }: GridProps) => {
return { verticalLines, horizontalLines };
}, [gridSize, bounds, machineInfo]);
- // Detect dark mode
- const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
- const gridColor = isDarkMode ? '#404040' : '#e0e0e0';
+ const gridColor = canvasColors.grid();
return (
<Group name="grid">
@@ -63,8 +62,7 @@ export const Grid = memo(({ gridSize, bounds, machineInfo }: GridProps) => {
Grid.displayName = 'Grid';
export const Origin = memo(() => {
- const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
- const originColor = isDarkMode ? '#999' : '#888';
+ const originColor = canvasColors.origin();
return (
<Group name="origin">
@@ -84,6 +82,7 @@ export const Hoop = memo(({ machineInfo }: HoopProps) => {
const { maxWidth, maxHeight } = machineInfo;
const hoopLeft = -maxWidth / 2;
const hoopTop = -maxHeight / 2;
+ const hoopColor = canvasColors.hoop();
return (
<Group name="hoop">
@@ -92,7 +91,7 @@ export const Hoop = memo(({ machineInfo }: HoopProps) => {
y={hoopTop}
width={maxWidth}
height={maxHeight}
- stroke="#2196F3"
+ stroke={hoopColor}
strokeWidth={3}
dash={[10, 5]}
/>
@@ -103,7 +102,7 @@ export const Hoop = memo(({ machineInfo }: HoopProps) => {
fontSize={14}
fontFamily="sans-serif"
fontStyle="bold"
- fill="#2196F3"
+ fill={hoopColor}
/>
</Group>
);
@@ -119,6 +118,7 @@ export const PatternBounds = memo(({ bounds }: PatternBoundsProps) => {
const { minX, maxX, minY, maxY } = bounds;
const width = maxX - minX;
const height = maxY - minY;
+ const boundsColor = canvasColors.bounds();
return (
<Rect
@@ -126,7 +126,7 @@ export const PatternBounds = memo(({ bounds }: PatternBoundsProps) => {
y={minY}
width={width}
height={height}
- stroke="#ff0000"
+ stroke={boundsColor}
strokeWidth={2}
dash={[5, 5]}
/>
@@ -231,6 +231,7 @@ export const CurrentPosition = memo(({ currentStitchIndex, stitches }: CurrentPo
}
const [x, y] = stitches[currentStitchIndex];
+ const positionColor = canvasColors.position();
return (
<Group name="currentPosition">
@@ -238,14 +239,14 @@ export const CurrentPosition = memo(({ currentStitchIndex, stitches }: CurrentPo
x={x}
y={y}
radius={8}
- fill="rgba(255, 0, 0, 0.3)"
- stroke="#ff0000"
+ fill={`${positionColor}4d`}
+ stroke={positionColor}
strokeWidth={3}
/>
- <Line points={[x - 12, y, x - 3, y]} stroke="#ff0000" strokeWidth={2} />
- <Line points={[x + 12, y, x + 3, y]} stroke="#ff0000" strokeWidth={2} />
- <Line points={[x, y - 12, x, y - 3]} stroke="#ff0000" strokeWidth={2} />
- <Line points={[x, y + 12, x, y + 3]} stroke="#ff0000" strokeWidth={2} />
+ <Line points={[x - 12, y, x - 3, y]} stroke={positionColor} strokeWidth={2} />
+ <Line points={[x + 12, y, x + 3, y]} stroke={positionColor} strokeWidth={2} />
+ <Line points={[x, y - 12, x, y - 3]} stroke={positionColor} strokeWidth={2} />
+ <Line points={[x, y + 12, x, y + 3]} stroke={positionColor} strokeWidth={2} />
</Group>
);
});
diff --git a/src/components/MachineConnection.tsx b/src/components/MachineConnection.tsx
index af8cb5f..dee49b5 100644
--- a/src/components/MachineConnection.tsx
+++ b/src/components/MachineConnection.tsx
@@ -61,16 +61,16 @@ export function MachineConnection({
};
const statusBadgeColors = {
- idle: 'bg-cyan-100 dark:bg-cyan-900/30 text-cyan-800 dark:text-cyan-300',
- info: 'bg-cyan-100 dark:bg-cyan-900/30 text-cyan-800 dark:text-cyan-300',
- active: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-300',
- waiting: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-300',
- warning: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-300',
- complete: 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300',
- success: 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300',
- interrupted: 'bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300',
- error: 'bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300',
- danger: 'bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300',
+ idle: 'bg-info-100 dark:bg-info-900/30 text-info-800 dark:text-info-300',
+ info: 'bg-info-100 dark:bg-info-900/30 text-info-800 dark:text-info-300',
+ active: 'bg-warning-100 dark:bg-warning-900/30 text-warning-800 dark:text-warning-300',
+ waiting: 'bg-warning-100 dark:bg-warning-900/30 text-warning-800 dark:text-warning-300',
+ warning: 'bg-warning-100 dark:bg-warning-900/30 text-warning-800 dark:text-warning-300',
+ complete: 'bg-success-100 dark:bg-success-900/30 text-success-800 dark:text-success-300',
+ success: 'bg-success-100 dark:bg-success-900/30 text-success-800 dark:text-success-300',
+ interrupted: 'bg-danger-100 dark:bg-danger-900/30 text-danger-800 dark:text-danger-300',
+ error: 'bg-danger-100 dark:bg-danger-900/30 text-danger-800 dark:text-danger-300',
+ danger: 'bg-danger-100 dark:bg-danger-900/30 text-danger-800 dark:text-danger-300',
};
// Only show error info when connected AND there's an actual error
@@ -90,15 +90,15 @@ export function MachineConnection({
<button
onClick={onConnect}
- className="w-full flex items-center justify-center gap-2 px-3 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded font-semibold text-xs hover:bg-blue-700 dark:hover:bg-blue-600 active:bg-blue-800 dark:active:bg-blue-500 transition-colors cursor-pointer"
+ className="w-full flex items-center justify-center gap-2 px-3 py-2 bg-primary-600 dark:bg-primary-700 text-white rounded font-semibold text-xs hover:bg-primary-700 dark:hover:bg-primary-600 active:bg-primary-800 dark:active:bg-primary-500 transition-colors cursor-pointer"
>
Connect to Machine
</button>
</div>
) : (
- <div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-green-600 dark:border-green-500">
+ <div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-success-600 dark:border-success-500">
<div className="flex items-start gap-3 mb-3">
- <WifiIcon className="w-6 h-6 text-green-600 dark:text-green-400 flex-shrink-0 mt-0.5" />
+ <WifiIcon className="w-6 h-6 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Machine Info</h3>
<p className="text-xs text-gray-600 dark:text-gray-400">
@@ -110,21 +110,21 @@ export function MachineConnection({
{/* Error/Info Display */}
{errorInfo && (
errorInfo.isInformational ? (
- <div className="mb-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
+ <div className="mb-3 p-3 bg-info-50 dark:bg-info-900/20 border border-info-200 dark:border-info-800 rounded-lg">
<div className="flex items-start gap-2">
- <InformationCircleIcon className="w-4 h-4 text-blue-600 dark:text-blue-400 flex-shrink-0" />
+ <InformationCircleIcon className="w-4 h-4 text-info-600 dark:text-info-400 flex-shrink-0" />
<div className="flex-1 min-w-0">
- <div className="font-semibold text-blue-900 dark:text-blue-200 text-xs">{errorInfo.title}</div>
+ <div className="font-semibold text-info-900 dark:text-info-200 text-xs">{errorInfo.title}</div>
</div>
</div>
</div>
) : (
- <div className="mb-3 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
+ <div className="mb-3 p-3 bg-danger-50 dark:bg-danger-900/20 border border-danger-200 dark:border-danger-800 rounded-lg">
<div className="flex items-start gap-2">
- <span className="text-red-600 dark:text-red-400 flex-shrink-0">⚠️</span>
+ <span className="text-danger-600 dark:text-danger-400 flex-shrink-0">⚠️</span>
<div className="flex-1 min-w-0">
- <div className="font-semibold text-red-900 dark:text-red-200 text-xs mb-1">{errorInfo.title}</div>
- <div className="text-xs text-red-700 dark:text-red-300 font-mono">
+ <div className="font-semibold text-danger-900 dark:text-danger-200 text-xs mb-1">{errorInfo.title}</div>
+ <div className="text-xs text-danger-700 dark:text-danger-300 font-mono">
Error Code: 0x{machineError.toString(16).toUpperCase().padStart(2, '0')}
</div>
</div>
@@ -148,14 +148,14 @@ export function MachineConnection({
{/* Machine Info */}
{machineInfo && (
<div className="grid grid-cols-2 gap-2 text-xs mb-3">
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">Max Area</span>
<span className="font-semibold text-gray-900 dark:text-gray-100">
{(machineInfo.maxWidth / 10).toFixed(1)} × {(machineInfo.maxHeight / 10).toFixed(1)} mm
</span>
</div>
{machineInfo.totalCount !== undefined && (
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">Total Stitches</span>
<span className="font-semibold text-gray-900 dark:text-gray-100">
{machineInfo.totalCount.toLocaleString()}
diff --git a/src/components/PatternCanvas.tsx b/src/components/PatternCanvas.tsx
index 9869f2a..2b14a8d 100644
--- a/src/components/PatternCanvas.tsx
+++ b/src/components/PatternCanvas.tsx
@@ -213,8 +213,8 @@ export function PatternCanvas() {
setPatternOffset(newOffset.x, newOffset.y);
}, [setPatternOffset]);
- const borderColor = pesData ? 'border-teal-600 dark:border-teal-500' : 'border-gray-400 dark:border-gray-600';
- const iconColor = pesData ? 'text-teal-600 dark:text-teal-400' : 'text-gray-600 dark:text-gray-400';
+ const borderColor = pesData ? 'border-tertiary-600 dark:border-tertiary-500' : 'border-gray-400 dark:border-gray-600';
+ const iconColor = pesData ? 'text-tertiary-600 dark:text-tertiary-400' : 'text-gray-600 dark:text-gray-400';
return (
<div className={`lg:h-full bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 ${borderColor} flex flex-col`}>
@@ -231,7 +231,7 @@ export function PatternCanvas() {
)}
</div>
</div>
- <div className="relative w-full h-[400px] sm:h-[500px] lg:flex-1 lg:min-h-0 border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-900 overflow-hidden" ref={containerRef}>
+ <div className="relative w-full h-[400px] sm:h-[500px] lg:flex-1 lg:min-h-0 border border-gray-300 dark:border-gray-600 rounded bg-gray-200 dark:bg-gray-900 overflow-hidden" ref={containerRef}>
{containerSize.width > 0 && (
<Stage
width={containerSize.width}
@@ -392,7 +392,7 @@ export function PatternCanvas() {
</div>
)}
</div>
- <div className="text-sm font-semibold text-blue-600 dark:text-blue-400 mb-1">
+ <div className="text-sm font-semibold text-primary-600 dark:text-primary-400 mb-1">
X: {(localPatternOffset.x / 10).toFixed(1)}mm, Y: {(localPatternOffset.y / 10).toFixed(1)}mm
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 italic">
@@ -402,17 +402,17 @@ export function PatternCanvas() {
{/* Zoom Controls Overlay */}
<div className="absolute bottom-2 sm:bottom-5 right-2 sm:right-5 flex gap-1.5 sm:gap-2 items-center bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm px-2 sm:px-3 py-1.5 sm:py-2 rounded-lg shadow-lg z-10">
- <button className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 dark:hover:border-blue-600 hover:shadow-md hover:shadow-blue-600/30 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleCenterPattern} disabled={!pesData || patternUploaded || isUploading} title="Center Pattern in Hoop">
+ <button className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-primary-600 hover:text-white hover:border-primary-600 dark:hover:border-primary-600 hover:shadow-md hover:shadow-primary-600/30 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleCenterPattern} disabled={!pesData || patternUploaded || isUploading} title="Center Pattern in Hoop">
<ArrowsPointingInIcon className="w-4 h-4 sm:w-5 sm:h-5 dark:text-gray-200" />
</button>
- <button className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 dark:hover:border-blue-600 hover:shadow-md hover:shadow-blue-600/30 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleZoomIn} title="Zoom In">
+ <button className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-primary-600 hover:text-white hover:border-primary-600 dark:hover:border-primary-600 hover:shadow-md hover:shadow-primary-600/30 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleZoomIn} title="Zoom In">
<PlusIcon className="w-4 h-4 sm:w-5 sm:h-5 dark:text-gray-200" />
</button>
<span className="min-w-[40px] sm:min-w-[50px] text-center text-sm font-semibold text-gray-900 dark:text-gray-100 select-none">{Math.round(stageScale * 100)}%</span>
- <button className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 dark:hover:border-blue-600 hover:shadow-md hover:shadow-blue-600/30 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleZoomOut} title="Zoom Out">
+ <button className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-primary-600 hover:text-white hover:border-primary-600 dark:hover:border-primary-600 hover:shadow-md hover:shadow-primary-600/30 disabled:opacity-50 disabled:cursor-not-allowed" onClick={handleZoomOut} title="Zoom Out">
<MinusIcon className="w-4 h-4 sm:w-5 sm:h-5 dark:text-gray-200" />
</button>
- <button className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-blue-600 hover:text-white hover:border-blue-600 dark:hover:border-blue-600 hover:shadow-md hover:shadow-blue-600/30 disabled:opacity-50 disabled:cursor-not-allowed ml-1" onClick={handleZoomReset} title="Reset Zoom">
+ <button className="w-7 h-7 sm:w-8 sm:h-8 p-1 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded cursor-pointer transition-all flex items-center justify-center hover:bg-primary-600 hover:text-white hover:border-primary-600 dark:hover:border-primary-600 hover:shadow-md hover:shadow-primary-600/30 disabled:opacity-50 disabled:cursor-not-allowed ml-1" onClick={handleZoomReset} title="Reset Zoom">
<ArrowPathIcon className="w-4 h-4 sm:w-5 sm:h-5 dark:text-gray-200" />
</button>
</div>
diff --git a/src/components/PatternInfo.tsx b/src/components/PatternInfo.tsx
index efff798..cbb3d17 100644
--- a/src/components/PatternInfo.tsx
+++ b/src/components/PatternInfo.tsx
@@ -9,14 +9,14 @@ export function PatternInfo({ pesData, showThreadBlocks = false }: PatternInfoPr
return (
<>
<div className="grid grid-cols-3 gap-2 text-xs mb-2">
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">Size</span>
<span className="font-semibold text-gray-900 dark:text-gray-100">
{((pesData.bounds.maxX - pesData.bounds.minX) / 10).toFixed(1)} x{' '}
{((pesData.bounds.maxY - pesData.bounds.minY) / 10).toFixed(1)} mm
</span>
</div>
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">Stitches</span>
<span className="font-semibold text-gray-900 dark:text-gray-100">
{pesData.penStitches?.stitches.length.toLocaleString() || pesData.stitchCount.toLocaleString()}
@@ -30,7 +30,7 @@ export function PatternInfo({ pesData, showThreadBlocks = false }: PatternInfoPr
)}
</span>
</div>
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">
{showThreadBlocks ? 'Colors / Blocks' : 'Colors'}
</span>
diff --git a/src/components/PatternPreviewPlaceholder.tsx b/src/components/PatternPreviewPlaceholder.tsx
index 2cebbd4..b46bdd2 100644
--- a/src/components/PatternPreviewPlaceholder.tsx
+++ b/src/components/PatternPreviewPlaceholder.tsx
@@ -15,8 +15,8 @@ export function PatternPreviewPlaceholder() {
<svg className="w-28 h-28 mx-auto text-gray-300 dark:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
- <div className="absolute -top-2 -right-2 w-8 h-8 bg-blue-100 dark:bg-blue-900 rounded-full flex items-center justify-center">
- <svg className="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+ <div className="absolute -top-2 -right-2 w-8 h-8 bg-primary-100 dark:bg-primary-900 rounded-full flex items-center justify-center">
+ <svg className="w-5 h-5 text-primary-600 dark:text-primary-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</div>
@@ -27,15 +27,15 @@ export function PatternPreviewPlaceholder() {
</p>
<div className="flex items-center justify-center gap-6 text-xs text-gray-400 dark:text-gray-500">
<div className="flex items-center gap-1">
- <div className="w-2 h-2 bg-blue-400 dark:bg-blue-500 rounded-full"></div>
+ <div className="w-2 h-2 bg-primary-400 dark:bg-primary-500 rounded-full"></div>
<span>Drag to Position</span>
</div>
<div className="flex items-center gap-1">
- <div className="w-2 h-2 bg-green-400 dark:bg-green-500 rounded-full"></div>
+ <div className="w-2 h-2 bg-success-400 dark:bg-success-500 rounded-full"></div>
<span>Zoom & Pan</span>
</div>
<div className="flex items-center gap-1">
- <div className="w-2 h-2 bg-purple-400 dark:bg-purple-500 rounded-full"></div>
+ <div className="w-2 h-2 bg-accent-400 dark:bg-accent-500 rounded-full"></div>
<span>Real-time Preview</span>
</div>
</div>
diff --git a/src/components/PatternSummaryCard.tsx b/src/components/PatternSummaryCard.tsx
index a094f35..cd809e0 100644
--- a/src/components/PatternSummaryCard.tsx
+++ b/src/components/PatternSummaryCard.tsx
@@ -34,9 +34,9 @@ export function PatternSummaryCard() {
const canDelete = canDeletePattern(machineStatus);
return (
- <div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-blue-600 dark:border-blue-500">
+ <div className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-primary-600 dark:border-primary-500">
<div className="flex items-start gap-3 mb-3">
- <DocumentTextIcon className="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
+ <DocumentTextIcon className="w-6 h-6 text-primary-600 dark:text-primary-400 flex-shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">Active Pattern</h3>
<p className="text-xs text-gray-600 dark:text-gray-400 truncate" title={currentFileName}>
@@ -51,7 +51,7 @@ export function PatternSummaryCard() {
<button
onClick={deletePattern}
disabled={isDeleting}
- className="w-full flex items-center justify-center gap-2 px-3 py-2.5 sm:py-2 bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 rounded border border-red-300 dark:border-red-700 hover:bg-red-100 dark:hover:bg-red-900/30 text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
+ className="w-full flex items-center justify-center gap-2 px-3 py-2.5 sm:py-2 bg-danger-50 dark:bg-danger-900/20 text-danger-700 dark:text-danger-300 rounded border border-danger-300 dark:border-danger-700 hover:bg-danger-100 dark:hover:bg-danger-900/30 text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
>
{isDeleting ? (
<>
diff --git a/src/components/ProgressMonitor.tsx b/src/components/ProgressMonitor.tsx
index fe210ca..11c3692 100644
--- a/src/components/ProgressMonitor.tsx
+++ b/src/components/ProgressMonitor.tsx
@@ -154,22 +154,22 @@ export function ProgressMonitor() {
}, [colorBlocks]);
const stateIndicatorColors = {
- idle: "bg-blue-50 dark:bg-blue-900/20 border-blue-600",
- info: "bg-blue-50 dark:bg-blue-900/20 border-blue-600",
- active: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-500",
- waiting: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-500",
- warning: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-500",
- complete: "bg-green-50 dark:bg-green-900/20 border-green-600",
- success: "bg-green-50 dark:bg-green-900/20 border-green-600",
- interrupted: "bg-red-50 dark:bg-red-900/20 border-red-600",
- error: "bg-red-50 dark:bg-red-900/20 border-red-600",
- danger: "bg-red-50 dark:bg-red-900/20 border-red-600",
+ idle: "bg-info-50 dark:bg-info-900/20 border-info-600",
+ info: "bg-info-50 dark:bg-info-900/20 border-info-600",
+ active: "bg-warning-50 dark:bg-warning-900/20 border-warning-500",
+ waiting: "bg-warning-50 dark:bg-warning-900/20 border-warning-500",
+ warning: "bg-warning-50 dark:bg-warning-900/20 border-warning-500",
+ complete: "bg-success-50 dark:bg-success-900/20 border-success-600",
+ success: "bg-success-50 dark:bg-success-900/20 border-success-600",
+ interrupted: "bg-danger-50 dark:bg-danger-900/20 border-danger-600",
+ error: "bg-danger-50 dark:bg-danger-900/20 border-danger-600",
+ danger: "bg-danger-50 dark:bg-danger-900/20 border-danger-600",
};
return (
- <div className="lg:h-full bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-purple-600 dark:border-purple-500 flex flex-col lg:overflow-hidden">
+ <div className="lg:h-full bg-white dark:bg-gray-800 p-4 rounded-lg shadow-md border-l-4 border-accent-600 dark:border-accent-500 flex flex-col lg:overflow-hidden">
<div className="flex items-start gap-3 mb-3">
- <ChartBarIcon className="w-6 h-6 text-purple-600 dark:text-purple-400 flex-shrink-0 mt-0.5" />
+ <ChartBarIcon className="w-6 h-6 text-accent-600 dark:text-accent-400 flex-shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">
Sewing Progress
@@ -185,7 +185,7 @@ export function ProgressMonitor() {
{/* Pattern Info */}
{patternInfo && (
<div className="grid grid-cols-3 gap-2 text-xs mb-3">
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">
Total Stitches
</span>
@@ -193,7 +193,7 @@ export function ProgressMonitor() {
{totalStitches.toLocaleString()}
</span>
</div>
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">
Total Time
</span>
@@ -201,7 +201,7 @@ export function ProgressMonitor() {
{totalMinutes} min
</span>
</div>
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">
Speed
</span>
@@ -217,13 +217,13 @@ export function ProgressMonitor() {
<div className="mb-3">
<div className="h-3 bg-gray-300 dark:bg-gray-600 rounded-md overflow-hidden shadow-inner relative mb-2">
<div
- className="h-full bg-gradient-to-r from-purple-600 to-purple-700 dark:from-purple-600 dark:to-purple-800 transition-all duration-300 ease-out relative overflow-hidden after:absolute after:inset-0 after:bg-gradient-to-r after:from-transparent after:via-white/30 after:to-transparent after:animate-[shimmer_2s_infinite]"
+ className="h-full bg-gradient-to-r from-accent-600 to-accent-700 dark:from-accent-600 dark:to-accent-800 transition-all duration-300 ease-out relative overflow-hidden after:absolute after:inset-0 after:bg-gradient-to-r after:from-transparent after:via-white/30 after:to-transparent after:animate-[shimmer_2s_infinite]"
style={{ width: `${progressPercent}%` }}
/>
</div>
<div className="grid grid-cols-2 gap-2 text-xs mb-3">
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">
Current Stitch
</span>
@@ -232,7 +232,7 @@ export function ProgressMonitor() {
{totalStitches.toLocaleString()}
</span>
</div>
- <div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
+ <div className="bg-gray-200 dark:bg-gray-700/50 p-2 rounded">
<span className="text-gray-600 dark:text-gray-400 block">
Time
</span>
@@ -249,22 +249,22 @@ export function ProgressMonitor() {
(() => {
const iconMap = {
ready: (
- <ClockIcon className="w-5 h-5 text-blue-600 dark:text-blue-400" />
+ <ClockIcon className="w-5 h-5 text-info-600 dark:text-info-400" />
),
active: (
- <PlayIcon className="w-5 h-5 text-yellow-600 dark:text-yellow-400" />
+ <PlayIcon className="w-5 h-5 text-warning-600 dark:text-warning-400" />
),
waiting: (
- <PauseCircleIcon className="w-5 h-5 text-yellow-600 dark:text-yellow-400" />
+ <PauseCircleIcon className="w-5 h-5 text-warning-600 dark:text-warning-400" />
),
complete: (
- <CheckBadgeIcon className="w-5 h-5 text-green-600 dark:text-green-400" />
+ <CheckBadgeIcon className="w-5 h-5 text-success-600 dark:text-success-400" />
),
interrupted: (
- <PauseCircleIcon className="w-5 h-5 text-red-600 dark:text-red-400" />
+ <PauseCircleIcon className="w-5 h-5 text-danger-600 dark:text-danger-400" />
),
error: (
- <ExclamationCircleIcon className="w-5 h-5 text-red-600 dark:text-red-400" />
+ <ExclamationCircleIcon className="w-5 h-5 text-danger-600 dark:text-danger-400" />
),
};
@@ -297,7 +297,7 @@ export function ProgressMonitor() {
<div
ref={colorBlocksScrollRef}
onScroll={handleColorBlocksScroll}
- className="lg:absolute lg:inset-0 flex flex-col gap-2 lg:overflow-y-auto scroll-smooth pr-1 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-gray-100 dark:[&::-webkit-scrollbar-track]:bg-gray-700 [&::-webkit-scrollbar-thumb]:bg-blue-600 dark:[&::-webkit-scrollbar-thumb]:bg-blue-500 [&::-webkit-scrollbar-thumb]:rounded-full"
+ className="lg:absolute lg:inset-0 flex flex-col gap-2 lg:overflow-y-auto scroll-smooth pr-1 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-gray-100 dark:[&::-webkit-scrollbar-track]:bg-gray-700 [&::-webkit-scrollbar-thumb]:bg-primary-600 dark:[&::-webkit-scrollbar-thumb]:bg-primary-500 [&::-webkit-scrollbar-thumb]:rounded-full"
>
{colorBlocks.map((block, index) => {
const isCompleted = currentStitch >= block.endStitch;
@@ -319,10 +319,10 @@ export function ProgressMonitor() {
ref={isCurrent ? currentBlockRef : null}
className={`p-2.5 rounded-lg border-2 transition-all duration-300 ${
isCompleted
- ? "border-green-600 bg-green-50 dark:bg-green-900/20"
+ ? "border-success-600 bg-success-50 dark:bg-success-900/20"
: isCurrent
- ? "border-purple-600 bg-purple-50 dark:bg-purple-900/20 shadow-lg shadow-purple-600/20 animate-pulseGlow"
- : "border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800/50 opacity-70"
+ ? "border-accent-600 bg-accent-50 dark:bg-accent-900/20 shadow-lg shadow-accent-600/20 animate-pulseGlow"
+ : "border-gray-200 dark:border-gray-600 bg-gray-300 dark:bg-gray-800/50 opacity-70"
}`}
role="listitem"
aria-label={`Thread ${block.colorIndex + 1}, ${block.stitchCount} stitches, ${isCompleted ? "completed" : isCurrent ? "in progress" : "pending"}`}
@@ -374,12 +374,12 @@ export function ProgressMonitor() {
{/* Status icon */}
{isCompleted ? (
<CheckCircleIcon
- className="w-5 h-5 text-green-600 flex-shrink-0"
+ className="w-5 h-5 text-success-600 flex-shrink-0"
aria-label="Completed"
/>
) : isCurrent ? (
<ArrowRightIcon
- className="w-5 h-5 text-purple-600 flex-shrink-0 animate-pulse"
+ className="w-5 h-5 text-accent-600 flex-shrink-0 animate-pulse"
aria-label="In progress"
/>
) : (
@@ -394,7 +394,7 @@ export function ProgressMonitor() {
{isCurrent && (
<div className="mt-2 h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div
- className="h-full bg-purple-600 dark:bg-purple-500 transition-all duration-300 rounded-full"
+ className="h-full bg-accent-600 dark:bg-accent-500 transition-all duration-300 rounded-full"
style={{ width: `${blockProgress}%` }}
role="progressbar"
aria-valuenow={Math.round(blockProgress)}
@@ -423,7 +423,7 @@ export function ProgressMonitor() {
<button
onClick={resumeSewing}
disabled={isDeleting}
- className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2.5 sm:py-2 bg-blue-600 dark:bg-blue-700 text-white rounded font-semibold text-xs hover:bg-blue-700 dark:hover:bg-blue-600 active:bg-blue-800 dark:active:bg-blue-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
+ className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2.5 sm:py-2 bg-primary-600 dark:bg-primary-700 text-white rounded font-semibold text-xs hover:bg-primary-700 dark:hover:bg-primary-600 active:bg-primary-800 dark:active:bg-primary-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
aria-label="Resume sewing the current pattern"
>
<PlayIcon className="w-3.5 h-3.5" />
@@ -436,7 +436,7 @@ export function ProgressMonitor() {
<button
onClick={startSewing}
disabled={isDeleting}
- className="flex-[2] flex items-center justify-center gap-1.5 px-3 py-2.5 sm:py-2 bg-blue-600 dark:bg-blue-700 text-white rounded font-semibold text-xs hover:bg-blue-700 dark:hover:bg-blue-600 active:bg-blue-800 dark:active:bg-blue-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
+ className="flex-[2] flex items-center justify-center gap-1.5 px-3 py-2.5 sm:py-2 bg-primary-600 dark:bg-primary-700 text-white rounded font-semibold text-xs hover:bg-primary-700 dark:hover:bg-primary-600 active:bg-primary-800 dark:active:bg-primary-500 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
aria-label="Start sewing the pattern"
>
<PlayIcon className="w-3.5 h-3.5" />
diff --git a/src/components/SkeletonLoader.tsx b/src/components/SkeletonLoader.tsx
index 73b4f4d..a4582e7 100644
--- a/src/components/SkeletonLoader.tsx
+++ b/src/components/SkeletonLoader.tsx
@@ -23,7 +23,7 @@ export function PatternCanvasSkeleton() {
<div className="flex items-center justify-between mb-4 pb-2 border-b-2 border-gray-300 dark:border-gray-600">
<SkeletonLoader className="h-7 w-40" variant="text" />
</div>
- <div className="relative w-full h-[600px] border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-900 overflow-hidden">
+ <div className="relative w-full h-[600px] border border-gray-300 dark:border-gray-600 rounded bg-gray-200 dark:bg-gray-900 overflow-hidden">
<div className="flex items-center justify-center h-full">
<div className="text-center space-y-4">
<div className="relative w-24 h-24 mx-auto">
@@ -50,7 +50,7 @@ export function PatternInfoSkeleton() {
return (
<div className="mt-4">
<SkeletonLoader className="h-6 w-40 mb-4" variant="text" />
- <div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg space-y-3">
+ <div className="bg-gray-200 dark:bg-gray-900 p-4 rounded-lg space-y-3">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="flex justify-between">
<SkeletonLoader className="h-4 w-24" variant="text" />
@@ -73,7 +73,7 @@ export function MachineConnectionSkeleton() {
<SkeletonLoader className="h-4 w-16" variant="text" />
<SkeletonLoader className="h-8 w-32 rounded-lg" />
</div>
- <div className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg space-y-2">
+ <div className="bg-gray-200 dark:bg-gray-900 p-4 rounded-lg space-y-2">
<div className="flex justify-between">
<SkeletonLoader className="h-4 w-20" variant="text" />
<SkeletonLoader className="h-4 w-24" variant="text" />
diff --git a/src/components/WorkflowStepper.tsx b/src/components/WorkflowStepper.tsx
index 43d213b..da0d176 100644
--- a/src/components/WorkflowStepper.tsx
+++ b/src/components/WorkflowStepper.tsx
@@ -320,11 +320,11 @@ export function WorkflowStepper() {
return (
<div className="relative max-w-5xl mx-auto mt-2 lg:mt-4" role="navigation" aria-label="Workflow progress">
{/* Progress bar background */}
- <div className="absolute top-4 lg:top-5 left-0 right-0 h-0.5 lg:h-1 bg-blue-400/20 dark:bg-blue-600/20 rounded-full" style={{ left: '16px', right: '16px' }} />
+ <div className="absolute top-4 lg:top-5 left-0 right-0 h-0.5 lg:h-1 bg-primary-400/20 dark:bg-primary-600/20 rounded-full" style={{ left: '16px', right: '16px' }} />
{/* Progress bar fill */}
<div
- className="absolute top-4 lg:top-5 left-0 h-0.5 lg:h-1 bg-gradient-to-r from-green-500 to-blue-500 dark:from-green-600 dark:to-blue-600 transition-all duration-500 rounded-full"
+ className="absolute top-4 lg:top-5 left-0 h-0.5 lg:h-1 bg-gradient-to-r from-success-500 to-primary-500 dark:from-success-600 dark:to-primary-600 transition-all duration-500 rounded-full"
style={{
left: '16px',
width: `calc(${((currentStep - 1) / (steps.length - 1)) * 100}% - 16px)`
@@ -358,9 +358,9 @@ export function WorkflowStepper() {
className={`
w-8 h-8 lg:w-10 lg:h-10 rounded-full flex items-center justify-center font-bold text-xs transition-all duration-300 border-2 shadow-md
${step.id <= currentStep ? 'cursor-pointer hover:scale-110' : 'cursor-not-allowed'}
- ${isComplete ? 'bg-green-500 dark:bg-green-600 border-green-400 dark:border-green-500 text-white shadow-green-500/30 dark:shadow-green-600/30' : ''}
- ${isCurrent ? 'bg-blue-600 dark:bg-blue-700 border-blue-500 dark:border-blue-600 text-white scale-105 lg:scale-110 shadow-blue-600/40 dark:shadow-blue-700/40 ring-2 ring-blue-300 dark:ring-blue-500 ring-offset-2 dark:ring-offset-gray-900' : ''}
- ${isUpcoming ? 'bg-blue-700 dark:bg-blue-800 border-blue-500/30 dark:border-blue-600/30 text-blue-200/70 dark:text-blue-300/70' : ''}
+ ${isComplete ? 'bg-success-500 dark:bg-success-600 border-success-400 dark:border-success-500 text-white shadow-success-500/30 dark:shadow-success-600/30' : ''}
+ ${isCurrent ? 'bg-primary-600 dark:bg-primary-700 border-primary-500 dark:border-primary-600 text-white scale-105 lg:scale-110 shadow-primary-600/40 dark:shadow-primary-700/40 ring-2 ring-primary-300 dark:ring-primary-500 ring-offset-2 dark:ring-offset-gray-900' : ''}
+ ${isUpcoming ? 'bg-primary-700 dark:bg-primary-800 border-primary-500/30 dark:border-primary-600/30 text-primary-200/70 dark:text-primary-300/70' : ''}
${showPopover && popoverStep === step.id ? 'ring-4 ring-white dark:ring-gray-800' : ''}
`}
aria-label={`${step.label}: ${isComplete ? 'completed' : isCurrent ? 'current' : 'upcoming'}. Click for details.`}
@@ -377,7 +377,7 @@ export function WorkflowStepper() {
{/* Step label */}
<div className="mt-1 lg:mt-2 text-center">
<div className={`text-xs font-semibold leading-tight ${
- isCurrent ? 'text-white' : isComplete ? 'text-green-200 dark:text-green-300' : 'text-blue-300/70 dark:text-blue-400/70'
+ isCurrent ? 'text-white' : isComplete ? 'text-success-200 dark:text-success-300' : 'text-primary-300/70 dark:text-primary-400/70'
}`}>
{step.label}
</div>
@@ -400,35 +400,35 @@ export function WorkflowStepper() {
if (!content) return null;
const colorClasses = {
- info: 'bg-blue-50 dark:bg-blue-900/95 border-blue-600 dark:border-blue-500',
- success: 'bg-green-50 dark:bg-green-900/95 border-green-600 dark:border-green-500',
- warning: 'bg-yellow-50 dark:bg-yellow-900/95 border-yellow-600 dark:border-yellow-500',
- error: 'bg-red-50 dark:bg-red-900/95 border-red-600 dark:border-red-500',
- progress: 'bg-cyan-50 dark:bg-cyan-900/95 border-cyan-600 dark:border-cyan-500'
+ info: 'bg-info-50 dark:bg-info-900/95 border-info-600 dark:border-info-500',
+ success: 'bg-success-50 dark:bg-success-900/95 border-success-600 dark:border-success-500',
+ warning: 'bg-warning-50 dark:bg-warning-900/95 border-warning-600 dark:border-warning-500',
+ error: 'bg-danger-50 dark:bg-danger-900/95 border-danger-600 dark:border-danger-500',
+ progress: 'bg-info-50 dark:bg-info-900/95 border-info-600 dark:border-info-500'
};
const iconColorClasses = {
- info: 'text-blue-600 dark:text-blue-400',
- success: 'text-green-600 dark:text-green-400',
- warning: 'text-yellow-600 dark:text-yellow-400',
- error: 'text-red-600 dark:text-red-400',
- progress: 'text-cyan-600 dark:text-cyan-400'
+ info: 'text-info-600 dark:text-info-400',
+ success: 'text-success-600 dark:text-success-400',
+ warning: 'text-warning-600 dark:text-warning-400',
+ error: 'text-danger-600 dark:text-danger-400',
+ progress: 'text-info-600 dark:text-info-400'
};
const textColorClasses = {
- info: 'text-blue-900 dark:text-blue-200',
- success: 'text-green-900 dark:text-green-200',
- warning: 'text-yellow-900 dark:text-yellow-200',
- error: 'text-red-900 dark:text-red-200',
- progress: 'text-cyan-900 dark:text-cyan-200'
+ info: 'text-info-900 dark:text-info-200',
+ success: 'text-success-900 dark:text-success-200',
+ warning: 'text-warning-900 dark:text-warning-200',
+ error: 'text-danger-900 dark:text-danger-200',
+ progress: 'text-info-900 dark:text-info-200'
};
const descColorClasses = {
- info: 'text-blue-800 dark:text-blue-300',
- success: 'text-green-800 dark:text-green-300',
- warning: 'text-yellow-800 dark:text-yellow-300',
- error: 'text-red-800 dark:text-red-300',
- progress: 'text-cyan-800 dark:text-cyan-300'
+ info: 'text-info-800 dark:text-info-300',
+ success: 'text-success-800 dark:text-success-300',
+ warning: 'text-warning-800 dark:text-warning-300',
+ error: 'text-danger-800 dark:text-danger-300',
+ progress: 'text-info-800 dark:text-info-300'
};
const listColorClasses = {
diff --git a/src/index.css b/src/index.css
index 1cd48aa..c969a03 100644
--- a/src/index.css
+++ b/src/index.css
@@ -5,8 +5,15 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
- background-color: #f5f5f5;
- color: #212529;
+ background-color: var(--color-gray-50);
+ color: var(--color-gray-900);
+}
+
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: var(--color-gray-900);
+ color: var(--color-gray-50);
+ }
}
code {
diff --git a/src/styles/designTokens.ts b/src/styles/designTokens.ts
deleted file mode 100644
index 50b53ee..0000000
--- a/src/styles/designTokens.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/**
- * Design Tokens - Semantic Color System
- *
- * These tokens provide meaningful names for colors used throughout the app.
- * Instead of using arbitrary colors like "blue-600", use semantic names that
- * describe the purpose: "primary", "success", "warning", etc.
- */
-
-export const colors = {
- // Primary - Main brand color for primary actions
- primary: {
- DEFAULT: '#2563eb', // blue-600
- hover: '#1d4ed8', // blue-700
- active: '#1e40af', // blue-800
- light: '#dbeafe', // blue-50
- border: '#93c5fd', // blue-300
- },
-
- // Success - Positive states, completion
- success: {
- DEFAULT: '#16a34a', // green-600
- hover: '#15803d', // green-700
- active: '#166534', // green-800
- light: '#dcfce7', // green-50
- border: '#86efac', // green-300
- },
-
- // Warning - Caution, waiting states
- warning: {
- DEFAULT: '#f59e0b', // amber-500
- hover: '#d97706', // amber-600
- active: '#b45309', // amber-700
- light: '#fef3c7', // amber-50
- border: '#fcd34d', // amber-300
- },
-
- // Danger - Errors, destructive actions
- danger: {
- DEFAULT: '#dc2626', // red-600
- hover: '#b91c1c', // red-700
- active: '#991b1b', // red-800
- light: '#fee2e2', // red-50
- border: '#fca5a5', // red-300
- },
-
- // Info - Informational states
- info: {
- DEFAULT: '#0891b2', // cyan-600
- hover: '#0e7490', // cyan-700
- active: '#155e75', // cyan-800
- light: '#cffafe', // cyan-50
- border: '#67e8f9', // cyan-300
- },
-
- // Neutral - Secondary actions, borders, backgrounds
- neutral: {
- DEFAULT: '#4b5563', // gray-600
- hover: '#374151', // gray-700
- active: '#1f2937', // gray-800
- light: '#f9fafb', // gray-50
- border: '#d1d5db', // gray-300
- text: '#6b7280', // gray-500
- },
-};
-
-/**
- * Button Classes - Reusable button styles
- */
-export const buttonClasses = {
- // Base styles for all buttons
- base: 'px-4 py-2.5 rounded-lg font-semibold text-sm transition-all duration-150 cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed',
-
- // Primary button
- primary: `bg-[${colors.primary.DEFAULT}] text-white hover:bg-[${colors.primary.hover}] active:bg-[${colors.primary.active}] hover:shadow-lg active:scale-[0.98] focus:ring-[${colors.primary.border}]`,
-
- // Success button
- success: `bg-[${colors.success.DEFAULT}] text-white hover:bg-[${colors.success.hover}] active:bg-[${colors.success.active}] hover:shadow-lg active:scale-[0.98] focus:ring-[${colors.success.border}]`,
-
- // Warning button
- warning: `bg-[${colors.warning.DEFAULT}] text-white hover:bg-[${colors.warning.hover}] active:bg-[${colors.warning.active}] hover:shadow-lg active:scale-[0.98] focus:ring-[${colors.warning.border}]`,
-
- // Danger button
- danger: `bg-[${colors.danger.DEFAULT}] text-white hover:bg-[${colors.danger.hover}] active:bg-[${colors.danger.active}] hover:shadow-lg active:scale-[0.98] focus:ring-[${colors.danger.border}]`,
-
- // Secondary/Neutral button
- secondary: `bg-[${colors.neutral.DEFAULT}] text-white hover:bg-[${colors.neutral.hover}] active:bg-[${colors.neutral.active}] hover:shadow-lg active:scale-[0.98] focus:ring-[${colors.neutral.border}]`,
-};
-
-/**
- * Typography Scale
- */
-export const typography = {
- // Headings
- h1: 'text-2xl font-bold',
- h2: 'text-xl font-semibold',
- h3: 'text-lg font-semibold',
- h4: 'text-base font-semibold',
-
- // Body text
- body: 'text-sm', // 14px - standard body
- bodyLarge: 'text-base', // 16px
- bodySmall: 'text-xs', // 12px - minimum size
-
- // Labels
- label: 'text-xs font-medium text-gray-600',
-
- // Values
- value: 'text-sm font-semibold text-gray-900',
-};
-
-/**
- * Spacing Scale
- */
-export const spacing = {
- xs: 'gap-2', // 8px
- sm: 'gap-3', // 12px
- md: 'gap-4', // 16px
- lg: 'gap-6', // 24px
-
- // Padding
- paddingXs: 'p-2', // 8px
- paddingSm: 'p-3', // 12px
- paddingMd: 'p-4', // 16px
- paddingLg: 'p-6', // 24px
-};
-
-/**
- * Alert/Status Box Classes
- */
-export const alertClasses = {
- success: `bg-[${colors.success.light}] text-green-800 border border-[${colors.success.border}] px-3 py-2 rounded-lg text-sm`,
- warning: `bg-[${colors.warning.light}] text-amber-800 border border-[${colors.warning.border}] px-3 py-2 rounded-lg text-sm`,
- danger: `bg-[${colors.danger.light}] text-red-800 border border-[${colors.danger.border}] px-3 py-2 rounded-lg text-sm`,
- info: `bg-[${colors.info.light}] text-cyan-800 border border-[${colors.info.border}] px-3 py-2 rounded-lg text-sm`,
-};
diff --git a/src/utils/cssVariables.ts b/src/utils/cssVariables.ts
new file mode 100644
index 0000000..512cd2e
--- /dev/null
+++ b/src/utils/cssVariables.ts
@@ -0,0 +1,19 @@
+/**
+ * Get CSS variable value from document root
+ */
+export function getCSSVariable(name: string): string {
+ return getComputedStyle(document.documentElement)
+ .getPropertyValue(name)
+ .trim();
+}
+
+/**
+ * Canvas color helpers
+ */
+export const canvasColors = {
+ grid: () => getCSSVariable('--color-canvas-grid'),
+ origin: () => getCSSVariable('--color-canvas-origin'),
+ hoop: () => getCSSVariable('--color-canvas-hoop'),
+ bounds: () => getCSSVariable('--color-canvas-bounds'),
+ position: () => getCSSVariable('--color-canvas-position'),
+};
Served by rngit 1.3.3 - Generated in 0.21s